﻿using System;
using System.Collections.Generic;

namespace OCXS.OCXSServiceLibrary.Utils {

    /// <summary>
    /// Represents a generic First-In, First-Out collection of objects limited in size.
    /// </summary>
    /// <remarks>
    /// <para>
    /// As long as maximum size is reached, older elements will be discarded giving way to newer ones.
    /// </para>
    /// <para>
    /// <c>LimitedBuffer</c> behaves like a circular queue, where new elements are inserted in the current Point of Write (PoW)
    /// and the latest inserted element is fetched from the current Point of Entry (PoE).
    /// </para>
    /// </remarks>
    /// <typeparam name="T">The type of objects in the queue.</typeparam>
    public class LimitedBuffer<T> {

        /// <summary>
        /// Default maximum container's size (circular buffer length).
        /// </summary>
        public const int DefaultSize = 10;

        /// <summary>
        /// The underlying container.
        /// </summary>
        private T[] buffer;

        /// <summary>
        /// Point of Entry, used to access the container for reading.
        /// </summary>
        private int poe;

        /// <summary>
        /// Point of Write, used to access the container for writing.
        /// </summary>
        private int pow;

        /// <summary>
        /// Default Constructor.
        /// </summary>
        /// <remarks>
        /// All objects are initialized to their respective default values, according to the provided <c>T</c> type.
        /// </remarks>
        /// <param name="size">
        /// Maximum container's size. If not provided <c>DefaultSize</c> is used.
        /// </param>
        public LimitedBuffer(int size = DefaultSize) {
            // Buffer instantiation
            this.buffer = new T[size];

            // Value initialization
            for (int i = 0; i < this.buffer.Length; i++)
                this.buffer[i] = default(T);
            this.pow = 0;
            this.poe = size - 1;
        }

        /// <summary>
        /// Handles either Point of Entry (PoE) and Point of Write (PoW) cursors behaviour.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Causes PoW and PoE to shift.
        /// </para>
        /// <para>
        /// PoE points always the previous position than PoW.
        /// </para>
        /// <para>
        /// When maximum size is reached PoW is brought back to initial position and PoE
        /// points the last one.
        /// </para>
        /// </remarks>
        private void ShiftCursors() {
            if (this.pow == this.buffer.Length - 1) {
                this.pow = 0;
                this.poe = this.buffer.Length - 1;
            } else
                this.poe = this.pow++;
        }

        /// <summary>
        /// Gets or sets the most recent value in container.
        /// </summary>
        /// <remarks>
        /// On set, <c>ShiftCursors</c> will be called.
        /// </remarks>
        public T Value {

            get {
                return this.buffer[this.poe];
            }

            set {
                this.buffer[this.pow] = value;
                this.ShiftCursors();
            }

        }

        /// <summary>
        /// Returns a <c>string</c> representing <c>LimitedBuffer</c> content.
        /// </summary>
        /// <returns>A <c>string</c> representing <c>LimitedBuffer</c> content.</returns>
        public override string ToString() {

            string ret = "LimitedBuffer = { ";

            foreach (T el in this.buffer) {
                ret += el.ToString() + "; ";
            }

            return ret + "}";

        }

    } /* LimitedQueue */

    /// <summary>
    /// Represents the Last Changed Value (LCV) of an OPC Item in the
    /// OPC SubscriptionPolledRefresh Service call.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This type is used only in SubscriptionPolledRefresh's context.
    /// </para>
    /// <para>
    /// This type is used as a container.
    /// </para>
    /// </remarks>
    public struct PolledRefreshVariableLCV {

        /// <summary>
        /// OPC Item's ItemPath.
        /// </summary>
        private string path;

        /// <summary>
        /// OPC Item's ItemName.
        /// </summary>
        private string name;

        /// <summary>
        /// OPC Item's ItemValue.
        /// </summary>
        /// <remarks>
        /// This is a <c>float</c> because of OCXS typing policy system.
        /// </remarks>
        private float value;

        /// <summary>
        /// Specifies whether or not the returned value has changed.
        /// </summary>
        private bool changed;

        /// <summary>
        /// Gets the OPC Item's ItemPath.
        /// </summary>
        public string Path {
            get { return this.path; }
        }

        /// <summary>
        /// Gets the OPC Item's ItemName.
        /// </summary>
        public string Name {
            get { return this.name; }
        }

        /// <summary>
        /// Gets the OPC Item's ItemValue.
        /// </summary>
        public float Value {
            get { return this.value; }
        }

        /// <summary>
        /// Gets the current <c>changed</c> status.
        /// </summary>
        public bool Changed {
            get { return this.changed; }
        }

        /// <summary>
        /// Default Constructor.
        /// </summary>
        /// <remarks>
        /// Fills the container with provided values.
        /// </remarks>
        /// <param name="path">The OPC Item's item path.</param>
        /// <param name="name">The OPC Item's item name.</param>
        /// <param name="value">The OPC Item's item value.</param>
        /// <param name="changed">The OPC Item's changed status.</param>
        public PolledRefreshVariableLCV(string path, string name, float value, bool changed) {
            this.name = name;
            this.path = path;
            this.value = value;
            this.changed = changed;
        }

    } /* PolledRefreshVariableLCV */

} /* OCXS.OCXSServiceLibrary.Utils */
